/*
 * Copyright 2004-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package susano.presen.controller;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import susano.common.exception.SusanoRuntimeException;
import susano.common.util.BigDecimalUtil;
import susano.common.util.BooleanUtil;
import susano.common.util.ByteUtil;
import susano.common.util.DoubleUtil;
import susano.common.util.FloatUtil;
import susano.common.util.IntegerUtil;
import susano.common.util.LongUtil;
import susano.common.util.NumberUtil;
import susano.common.util.ShortUtil;
import susano.common.util.StringUtil;
import susano.presen.util.DateUtil;
import susano.presen.util.ThrowableUtil;
import susano.presen.validator.Errors;

import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;

/**
 * base action
 *
 * @author tanaka.akira.2006
 * @version $Id$
 *
 */
public abstract class Action {

	private static final Logger logger = Logger.getLogger(Action.class
			.getName());

	protected ServletContext application;
	protected HttpServletRequest request;
	protected HttpServletResponse response;
	public String basePath;
	protected Errors errors;

	/** whether this request is for init-page */
	private boolean init = false;

	/**
	 * Execute the bare action process.
	 *
	 * @return the navigation
	 */
	public Navigation doExecute() {
		Navigation navigation = null;
		Throwable error = null;

		try {
			// #init
			if (isGet() && !hasParams()) {
				init = true;
				navigation = init();
			} else {
				init = false;
			}

			// #validate
			if (navigation == null) {
				navigation = validate();
			}

			// #execute
			if (errors.isEmpty() && !init) {
				navigation = execute();
			}
		} catch (Throwable t) {
			if (logger.isLoggable(Level.WARNING)) {
				logger.log(Level.WARNING, t.getMessage(), t);
			}
			error = t;
		} finally {
			try {
				tearDown();
			} catch (Throwable t) {
				if (logger.isLoggable(Level.WARNING)) {
					logger.log(Level.WARNING, t.getMessage(), t);
				}
				if (error == null) {
					error = t;
				}
			}
		}

		applyPublicFieldBeanToEl();

		if (error != null) {
			navigation = handleError(error);
		}
		return navigation;
	}

	/**
	 * apply the public-field bean to EL.
	 */
	private void applyPublicFieldBeanToEl() {
		// TODO Auto-generated method stub

	}

	/**
	 * Override to execute this action
	 *
	 * @return the navigation.
	 */
	protected abstract Navigation execute();

	/**
	 * Override to validate
	 *
	 * @return the navigation on error
	 */
	protected abstract Navigation validate();

	/**
	 * Override to show the initial page.
	 *
	 * @return the navigation for showing the initial page
	 */
	protected abstract Navigation init();

	/**
	 * Tears down this action. This method is called after "run" method is
	 * called.
	 *
	 */
	protected void tearDown() {
		for (Transaction tx : DatastoreServiceFactory.getDatastoreService()
				.getActiveTransactions()) {
			tx.rollback();
		}
	}

	/**
	 * Handles the error.
	 *
	 * @param error
	 *            the error
	 * @return the navigation.
	 */
	protected Navigation handleError(Throwable error) {
		if (error instanceof Error) {
			throw (Error) error;
		}
		if (error instanceof RuntimeException) {
			throw (RuntimeException) error;
		}
		throw new SusanoRuntimeException(error);
	}

	/**
	 * Creates a new {@link Navigation} for "forward".
	 *
	 * @param path
	 *            the action-relative path
	 * @return a new {@link Navigation}
	 */
	protected Navigation forward(String path) {
		return new Navigation(path, false);
	}

	/**
	 * Creates a new {@link Navigation} for "redirect".
	 *
	 * @param path
	 *            the action-relative path
	 * @return a new {@link Navigation}
	 */
	protected Navigation redirect(String path) {
		return new Navigation(path, true);
	}

	/**
	 * Returns the request parameter.
	 *
	 * @param name
	 *            the parameter name
	 * @return the parameter value
	 */
	protected String param(String name) {
		return request.getParameter(name);
	}

	/**
	 * Returns the request parameter.
	 *
	 * @param name
	 *            the parameter name
	 * @return the parameter value
	 */
	protected String[] paramValues(String name) {
		return request.getParameterValues(name);
	}

	/**
	 * Returns the request attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the request attribute
	 */
	@SuppressWarnings("unchecked")
	protected <T> T requestScope(String name) {
		return (T) request.getAttribute(name);
	}

	/**
	 * Sets the request attribute.
	 *
	 * @param name
	 *            the attribute name
	 * @param value
	 *            the attribute value
	 */
	protected void requestScope(String name, Object value) {
		request.setAttribute(name, value);
	}

	/**
	 * Removes the request attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the removed value
	 */
	@SuppressWarnings("unchecked")
	protected <T> T removeRequestScope(String name) {
		T value = (T) request.getAttribute(name);
		request.removeAttribute(name);
		return value;
	}

	/**
	 * Returns the session attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the attribute value
	 */
	@SuppressWarnings("unchecked")
	protected <T> T sessionScope(String name) {
		HttpSession session = request.getSession(false);
		if (session == null) {
			return null;
		}
		return (T) session.getAttribute(name);
	}

	/**
	 * Sets the session attribute.
	 *
	 * @param name
	 *            the attribute name
	 * @param value
	 *            the attribute value
	 */
	protected void sessionScope(String name, Object value) {
		request.getSession().setAttribute(name, value);
	}

	/**
	 * Removes the session attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the removed value
	 */
	@SuppressWarnings("unchecked")
	protected <T> T removeSessionScope(String name) {
		HttpSession session = request.getSession(false);
		if (session == null) {
			return null;
		}
		T value = (T) session.getAttribute(name);
		session.removeAttribute(name);
		return value;
	}

	/**
	 * Returns the servlet context attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the attribute value
	 */
	@SuppressWarnings("unchecked")
	protected <T> T applicationScope(String name) {
		return (T) application.getAttribute(name);
	}

	/**
	 * Sets the servlet context attribute.
	 *
	 * @param name
	 *            the attribute name
	 * @param value
	 *            the attribute value
	 */
	protected void applicationScope(String name, Object value) {
		application.setAttribute(name, value);
	}

	/**
	 * Removes the servlet context attribute.
	 *
	 * @param <T>
	 *            the return type
	 * @param name
	 *            the attribute name
	 * @return the removed value
	 */
	@SuppressWarnings("unchecked")
	protected <T> T removeApplicationScope(String name) {
		T value = (T) application.getAttribute(name);
		application.removeAttribute(name);
		return value;
	}

	/**
	 * Determines if this application is running on the development server.
	 *
	 * @return whether this application is running on the development server
	 */
	protected boolean isDevelopment() {
		return application.getServerInfo().indexOf("Development") >= 0;
	}

	/**
	 * Downloads the data.
	 *
	 * @param fileName
	 *            the file name
	 * @param data
	 *            the data
	 */
	protected void download(String fileName, byte[] data) {
		try {
			response.setContentType("application/octet-stream");
			response.setHeader("Content-disposition", "attachment; filename=\""
					+ fileName + "\"");
			OutputStream out = response.getOutputStream();
			try {
				out.write(data);
			} finally {
				out.close();
			}
		} catch (IOException e) {
			ThrowableUtil.wrapAndThrow(e);
		}
	}

	/**
	 * Determines if this request is get method.
	 *
	 * @return whether this request is get method
	 */
	protected boolean isGet() {
		return "get".equalsIgnoreCase(request.getMethod());
	}

	/**
	 * Determines if this request is post method.
	 *
	 * @return whether this request is post method
	 */
	protected boolean isPost() {
		return "post".equalsIgnoreCase(request.getMethod());
	}

	/**
	 * Determines if this request has request parameters.
	 *
	 * @return whether this request has request parameters.
	 */
	protected boolean hasParams() {
		if (request.getParameterMap().size() >= 1) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns the request attribute value as byte.
	 *
	 * @param name
	 *            the attribute name
	 * @return the byte attribute value
	 */
	protected Byte asByte(String name) {
		return ByteUtil.toByte(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as byte.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the byte attribute value
	 */
	protected Byte asByte(String name, String pattern) {
		return ByteUtil.toByte(NumberUtil.toNumber(asString(name), pattern));
	}

	/**
	 * Returns the request attribute value as short.
	 *
	 * @param name
	 *            the attribute name
	 * @return the short attribute value
	 */
	protected Short asShort(String name) {
		return ShortUtil.toShort(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as short.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the short attribute value
	 */
	protected Short asShort(String name, String pattern) {
		return ShortUtil.toShort(NumberUtil.toNumber(asString(name), pattern));
	}

	/**
	 * Returns the request attribute value as integer.
	 *
	 * @param name
	 *            the attribute name
	 * @return the integer attribute value
	 */
	protected Integer asInteger(String name) {
		return IntegerUtil.toInteger(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as integer.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the integer attribute value
	 */
	protected Integer asInteger(String name, String pattern) {
		return IntegerUtil.toInteger(NumberUtil.toNumber(asString(name),
				pattern));
	}

	/**
	 * Returns the request attribute value as long.
	 *
	 * @param name
	 *            the attribute name
	 * @return the long attribute value
	 */
	protected Long asLong(String name) {
		return LongUtil.toLong(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as long.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the long attribute value
	 */
	protected Long asLong(String name, String pattern) {
		return LongUtil.toLong(NumberUtil.toNumber(asString(name), pattern));
	}

	/**
	 * Returns the request attribute value as float.
	 *
	 * @param name
	 *            the attribute name
	 * @return the float attribute value
	 */
	protected Float asFloat(String name) {
		return FloatUtil.toFloat(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as float.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the float attribute value
	 */
	protected Float asFloat(String name, String pattern) {
		return FloatUtil.toFloat(NumberUtil.toNumber(asString(name), pattern));
	}

	/**
	 * Returns the request attribute value as double.
	 *
	 * @param name
	 *            the attribute name
	 * @return the double attribute value
	 */
	protected Double asDouble(String name) {
		return DoubleUtil.toDouble(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as double.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the double attribute value
	 */
	protected Double asDouble(String name, String pattern) {
		return DoubleUtil
				.toDouble(NumberUtil.toNumber(asString(name), pattern));
	}

	/**
	 * Returns the request attribute value as big decimal.
	 *
	 * @param name
	 *            the attribute name
	 * @return the big decimal attribute value
	 */
	protected BigDecimal asBigDecimal(String name) {
		return BigDecimalUtil.toBigDecimal(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as big decimal.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link DecimalFormat}
	 * @return the big decimal attribute value
	 */
	protected BigDecimal asBigDecimal(String name, String pattern) {
		return BigDecimalUtil.toBigDecimal(NumberUtil.toNumber(asString(name),
				pattern));
	}

	/**
	 * Returns the request attribute value as string.
	 *
	 * @param name
	 *            the attribute name
	 * @return the string attribute value
	 */
	protected String asString(String name) {
		return StringUtil.toString(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as boolean.
	 *
	 * @param name
	 *            the attribute name
	 * @return the boolean attribute value
	 */
	protected Boolean asBoolean(String name) {
		return BooleanUtil.toBoolean(request.getAttribute(name));
	}

	/**
	 * Returns the request attribute value as date.
	 *
	 * @param name
	 *            the attribute name
	 * @param pattern
	 *            the pattern for {@link SimpleDateFormat}
	 * @return the date attribute value
	 */
	protected Date asDate(String name, String pattern) {
		return DateUtil.toDate(asString(name), pattern);
	}

	/**
	 * Returns the request attribute value as {@link Key}.
	 *
	 * @param name
	 *            the attribute name
	 * @return the request attribute value as {@link Key}
	 */
	protected Key asKey(String name) {
		Object key = request.getAttribute(name);
		if (key == null) {
			return null;
		}
		if (key instanceof Key) {
			return (Key) key;
		}
		return KeyFactory.stringToKey(key.toString());
	}
}